{ Contains shared declarations and simple routines }
unit CPBase;

interface

uses Graphics;

type
  { Basic float type }
  TCPFloat = Double;
  TCPFloatArray = array of TCPFloat;

  { 2 component float vector }
  TCPVector = record
  case Integer of
    0: (X: TCPFloat; //< X-coordinate
        Y: TCPFloat); //< Y-coordinate
    1: (C: array[0..1] of TCPFloat); //< X- & Y-coordinates as array
  end;

  { 2 component float vector }
  TCPPoint = TCPVector;

  { Rectangular bounds defined by position and dimension }
  TCPBounds = record
  case Integer of
    0: (Left, Top, Width, Height: TCPFloat); //< Dimensions of rectangular bounds
    1: (Position, Span: TCPPoint); //< Position of top left corner and horizontal and vertical size
    2: (C: array[0..3] of TCPFloat); //< Coordinates as array
  end;

  { Rectangle defined by two points }
  TCPRect = record
  case Integer of
    0: (Left, Top, Right, Bottom: TCPFloat); //< Corner coordinates
    1: (TopLeft, BottomRight: TCPPoint); //< Corner points
    2: (C: array[0..3] of TCPFloat); //< Coordinates as array
  end;

  { Colors of graphical elements in a plotter control. This record is part of
    TCPPlotStyle. }
  TCPColorTheme = record
  case Integer of
    0: (Background: TColor; //< Color for background of coordinate system
        Grid: TColor; //< Color grid layers, blended with the background color
        Axis: TColor; //< Color for coordinate axes
        AxisMark: TColor; //< Color for marks on coordinate axes
        AxisText: TColor; //< Color for textual descriptions of marks
        Guides: TColor); //< Color for user interaction guides
    1: (Colors: array[0..5] of TColor); //< Access to all colors as array
  end;

  { Style options for visual elements of plotter control }
  TCPPlotStyle = record
    { If enabled, the plotter control will draw with low details during
      view changes. }
    FastPreviewMode: Boolean;
    { If enabled, view changes in the plotter control are animated }
    UseAnimation: Boolean;
    { Step size between graph points in pt. A higher value will
      result in a better plotting accuracy. }
    GraphStepSize: Double;
    AnimDuration: Integer; //< Play length of animated view changes
    AnimFrameDelay: Integer; //< Delay between frames of view change animation
    Colors: TCPColorTheme;
    AxisMarksLength: Single; //< Length of axis marks in pt
    { Minimum cell size of main grid in pt. The visible cell size of the
      main grid will never be less than this value. }
    GridMinCellSize: Single;
    { Opactiy of grid lines between 0 (fully transparent) and 1
      (fully opaque) }
    GridOpacity: Single;
    GridLineWidth: Single; //< Thickness of grid lines in pt
    AxisTextPadding: Single; //< Distance of axis text to axis in pt
  end;

  { Pointer on color palette }
  PCPPlotStyle = ^TCPPlotStyle;

  { Settings for user interactions }
  TCPInputOptions = record
    ZoomWheel: Single; //< Zoom step for zooming on one mouse wheel scroll
    ZoomKeypress: Single; //< Zoom step for zooming on pressing + or - key
    ZoomMouse: Single; //< Zoom step for zooming on right mouse click
    { Scroll step in % of the control width on pressing cursor keys }
    PanKeypress: Single;
  end;

const
  { Default input opions for plotter control }
  CPDefaultInputOptions: TCPInputOptions =
  (ZoomWheel:    1 / 1;
   ZoomKeypress: 0.125;
   ZoomMouse:    0.03125;
   PanKeypress:  20 / 100);

  { Default visual style for plotter control }
  CPDefaultStyle: TCPPlotStyle =
  (FastPreviewMode: false;
   UseAnimation: true;
   GraphStepSize: 1.0 * (72 / 96); // 1px for 96 dpi displays
   AnimDuration: 250;
   AnimFrameDelay: 10;
   Colors:
   (Background: $00747270;
    Grid:       $00FFFFFF;
    Axis:       $00D0FFFF;
    AxisMark:   $00FFFFFF;
    AxisText:   $00400000;
    Guides:     $00000000);
   AxisMarksLength: 9.0;
   GridMinCellSize: 50.0;
   GridOpacity: 0.25;
   GridLineWidth:   0.75;
   AxisTextPadding: 3.0);

  CPBlackColors: TCPColorTheme =
  (Background: $00000000;
   Grid: $00FFFFFF;
   Axis: $00FFFFFF;
   AxisMark: $00FFFFFF;
   AxisText: $00FFFFFF;
   Guides: $00FFFFFF);

  CPWindowColors: TCPColorTheme =
  (Background: clBtnFace;
   Grid: cl3DDkShadow;
   Axis: clWindowText;
   AxisMark: clWindowText;
   AxisText: clWindowText;
   Guides: clHighlight);

  CPBrightColors: TCPColorTheme =
  (Background: $00FFFFFF;
   Grid: $00000000;
   Axis: $00000000;
   AxisMark: $00000000;
   AxisText: $00000000;
   Guides: $00000000);

  CPOldSkoolColors: TCPColorTheme =
  (Background: $00400000;
   Grid: $00FFFFFF;
   Axis: $0000FFFF;
   AxisMark: $00FFFFFF;
   AxisText: $00FFFFFF;
   Guides: $00FFFF00);

  CPInkOnPaperColors: TCPColorTheme =
  (Background: $00D8E0E8;
   Grid: $00000080;
   Axis: $00400000;
   AxisMark: $00400000;
   AxisText: $00804000;
   Guides: $00804000);

  CPCreamColors:  TCPColorTheme =
  (Background: $00F8F8F8;
   Grid: $004080C0;
   Axis: $0000E6AC;
   AxisMark: $0000E6AC;
   AxisText: $00FF8040;
   Guides: $004040FF);

  { Name of plotter control color themes
    @seealso CPColorThemes }
  CPColorThemes: array[0..6] of ^TCPColorTheme =
    (@CPDefaultStyle.Colors, @CPBlackColors,
     @CPWindowColors, @CPBrightColors, @CPOldSkoolColors, @CPInkOnPaperColors,
     @CPCreamColors);


{ Creates a TCPVector record }
function CPToVector(const X, Y: TCPFloat): TCPVector;
{ Creates a TCPBounds record}
function CPToBounds(const Left, Top, Width, Height: TCPFloat): TCPBounds;
{ Creates a TCPRect record}
function CPToRect(const Left, Top, Right, Bottom: TCPFloat): TCPRect;
{ Multiplicates a vector with a scalar value }
function CPMul(const V: TCPVector; const Factor: TCPFloat): TCPVector; overload;
{ Multiplicates two vectors component-wise }
function CPMul(const V1, V2: TCPVector): TCPVector; overload;
{ Adds two vectors }
function CPAdd(const V1, V2: TCPVector): TCPVector;
{ Subtracts the second vector from the first one }
function CPSub(const V1, V2: TCPVector): TCPVector;
{ Clamps and rounds float values, so that large values dont cause overruns }
function CPClipRound(X: TCPFloat): Integer;
{ Tests if the specified point lies in the rectangle.
  @param Rect Rectangle
  @param Pt Point
  @return Returns true, if the specified point lies in the rectangle; otherwise,
  false is returned. }
function CPPtInRect(const Rect: TCPRect; const Pt: TCPPoint): Boolean;
{ Calculate a mixed color between Col and ColP.
  @param ColorBack Background color
  @param ColorFront Foreground color
  @param Opacity Opacity of foreground color
  @returns Foreground color linearly blended over the background color with the
    specified opactity. An opacity of 0 will return the background color and
    an opacity of 1 will return the foreground color. }
function CPMergeColor(ColorBack, ColorFront: TColor;
  const Opacity: Single): TColor;
{ Calculates a linear interpolation between the first and the second value
  using the specfied Factor.
  @param Value1 First value
  @param Value2 Second value
  @param Factor Factor used for linear interpolation. If zero, then Value1 is
    returned, if one, then Value2 is returned. }
function CPLerp(const Value1, Value2, Factor: TCPFloat): TCPFloat;
{ Swaps the values of both arguments. }
procedure CPSwap(var Value1, Value2: TCPFloat);
{ Enables or disables floating point exceptions.
  @param Enabled State of floating points exceptions. }
procedure CPToggleFloatOpExceptions(Enabled: Boolean);
{ Converts a float value to a string using . as decimal operator. }
function FToStr(Value: TCPFloat): string;


implementation

uses SysUtils;

var
  { Contains the prior status of floating point exceptions.
    @seealso CPToggleFloatOpExceptions }
  Old8087CW: Word;

function CPToVector(const X, Y: TCPFloat): TCPVector;
begin
  Result.X := X;
  Result.Y := Y;
end;

function CPToBounds(const Left, Top, Width, Height: TCPFloat): TCPBounds;
begin
  Result.Left := Left;
  Result.Top := Top;
  Result.Width := Width;
  Result.Height := Height;
end;

function CPToRect(const Left, Top, Right, Bottom: TCPFloat): TCPRect;
begin
  Result.Left := Left;
  Result.Top := Top;
  Result.Right := Right;
  Result.Bottom := Bottom;
end;

function CPMul(const V: TCPVector; const Factor: TCPFloat): TCPVector;
begin
  Result.X := V.X * Factor;
  Result.Y := V.Y * Factor;
end;

function CPMul(const V1, V2: TCPVector): TCPVector;
begin
  Result.X := V1.X * V2.X;
  Result.Y := V1.Y * V2.Y;
end;

function CPAdd(const V1, V2: TCPVector): TCPVector;
begin
  Result.X := V1.X + V2.X;
  Result.Y := V1.Y + V2.Y;
end;

function CPSub(const V1, V2: TCPVector): TCPVector;
begin
  Result.X := V1.X - V2.X;
  Result.Y := V1.Y - V2.Y;
end;

function CPClipRound(X: TCPFloat): Integer;
begin
  If (X < Low(Smallint)) then
    Result := Low(Smallint)
  else
    If (X >= High(Smallint)) then
      Result := High(Smallint)
    else
      Result := Round(X);
end;

function CPPtInRect(const Rect: TCPRect; const Pt: TCPPoint): Boolean;
begin
  Result := (Pt.X >= Rect.Left) and (Pt.X < Rect.Right) and (Pt.Y >= Rect.Top)
    and (Pt.Y < Rect.Bottom);
end;

function CPMergeColor(ColorBack, ColorFront: TColor;
  const Opacity: Single): TColor;
type
  TColorChannels = array[0..3] of Byte;
var
  a: Integer;
begin
  ColorBack := ColorToRGB(ColorBack);
  ColorFront := ColorToRGB(ColorFront);
  for a := 0 to 2 do
    TColorChannels(Result)[a] := Round(
      TColorChannels(ColorBack)[a] + Opacity *
       (TColorChannels(ColorFront)[a] - TColorChannels(ColorBack)[a]));
  TColorChannels(Result)[3] := 0;
end;

function CPLerp(const Value1, Value2, Factor: TCPFloat): TCPFloat;
begin
  Result := Value1 + Factor * (Value2 - Value1);
end;

procedure CPSwap(var Value1, Value2: TCPFloat);
var
  T: TCPFloat;
begin
  T := Value1;
  Value1 := Value2;
  Value2 := T;
end;

procedure CPToggleFloatOpExceptions(Enabled: Boolean);
begin
  if (Enabled) then
    Set8087CW(Old8087CW)
  else
  begin
    Old8087CW := Default8087CW;
    Set8087CW($133F);
  end;
end;

function FToStr(Value: TCPFloat): String;
var OldDS:Char;
begin
  OldDS := DecimalSeparator;
  DecimalSeparator := '.';
  Result := FloatToStr(Value);
  DecimalSeparator := OldDS;
end;


end.


